package tech.fangsong.client.service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;

/**
 * 生成一个自增的64位长整形序列号
 */
public class SequenceService {
    private static final SequenceService sequenceService = new SequenceService();

    private SequenceService(){

    }
    /**
     * 基准时间 2024-01-01 00:00:00
     */
    private static final long BASE_TIME = 1704038400L;

    /**
     * 45位，时间
     */
    private static final long TIME_BITS = 45;
    /**
     * 18位，序号
     */
    private static final long SERIAL_BITS = 18;

    /**
     * 时间回溯等待期，超过此时间抛出异常
     */
    private static final long BACK_IN = 5L;

    /**
     * 上次时间
     */
    private long lastTime = -1L;

    /**
     * 序号
     */
    private long serial;

    private static final long MAX_SEQUENCE = (1L << SERIAL_BITS) - 1;

    private static final long MAX_VAL = (1L << TIME_BITS) - 1;

    public static SequenceService getInstance(){
        return sequenceService;
    }

    public synchronized long generateSequenceId(){
        long currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        if(currentTime > lastTime){
            serial = 0;
        }else if(currentTime == lastTime){
            if (serial < MAX_SEQUENCE) {
                serial++;
            } else {
                // 序列用完，等待时间进入下一秒
                currentTime = waitForNext();
                serial = 0;
            }
        }else {
            //等待时间归位
            currentTime = waitForNext();
            serial = 0;

        }

        lastTime = currentTime;
        long nowSeconds = currentTime - BASE_TIME;
        if(nowSeconds >= MAX_VAL){
            throw new IllegalStateException("时间超出范围");
        }
        return nowSeconds << SERIAL_BITS | serial;
    }

    /**
     * 等待时间进入下一秒
     * @return
     */
    private long waitForNext(){
        long currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        if(lastTime-currentTime>=BACK_IN){
            throw new IllegalStateException("时间回溯大于"+BACK_IN+"秒");
        }
        while (currentTime <= lastTime){
            currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        }
        return currentTime;
    }
}
